/* Copyright (C) 2008  Romolo Camplani, Marco Fortunato, Marco Marelli, Guido Rota, Fabio A. Schreiber et al.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */
package org.dei.perla.sys.device.channel.concrete.socket;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.UnknownHostException;

import org.dei.perla.sys.device.channel.Channel;
import org.dei.perla.sys.device.channel.frame.address.concrete.SocketFrameAddress;
import org.dei.perla.sys.device.channel.frame.receiver.FrameReceiverHeaderLess;
import org.dei.perla.sys.device.channel.frame.sender.FrameSenderHeaderLess;
import org.dei.perla.utils.logger.LogRecord.Type;
import org.dei.perla.utils.logger.LogRecord.Verbosity;

/**
 * Canale Socket
 */
public class SocketChannel extends Channel {

	/**
	 * Socket aperta
	 */
	private Socket pSocket;

	/**
	 * Configurazione della socket
	 */
	private SocketChannelConfiguration pConfiguration;

	/**
	 * Inizializza un canale su socket headerless
	 * 
	 * @param parName
	 * @param parFrameReceiver
	 * @param parFrameSender
	 * @param parConfiguration
	 */
	public SocketChannel(String parName, FrameReceiverHeaderLess parFrameReceiver,
			FrameSenderHeaderLess parFrameSender, SocketChannelConfiguration parConfiguration) {
		
		super(parName, parFrameReceiver, parFrameSender, null);
		this.pConfiguration = parConfiguration;
		this.pRemoteAddress = this.pConfiguration.getRemoteAddress();
		
	}
	
	/**
	 * Inizializza un canale su socket, disponendo di una socket già aperta
	 * 
	 * @param parName
	 * @param parFrameReceiver
	 * @param parFrameSender
	 * @param parSocket
	 */
	public SocketChannel(String parName, FrameReceiverHeaderLess parFrameReceiver,
			FrameSenderHeaderLess parFrameSender, Socket parSocket) {
		
		super(parName, parFrameReceiver, parFrameSender, null);
		this.pSocket = parSocket;
		
	}

	/**
	 * Funzione di apertura del canale
	 */
	protected void openChannel() {

		// Indica se si son verificati errori
		boolean tOk = true;
		
		String tSocketDescription = "";
		
		// Se la socket non è stata passata con il costruttore, creala
		if ( this.pSocket == null ) {
		
			// Imposta il tipo di canale
			this.setEventBasedInput(false);
	
			// Costruisce una stringa di descrizione per il socket
			tSocketDescription = "socket ";
			tSocketDescription += "(" + this.pConfiguration.getRemoteAddress().getAddress().getHostName() + ", "
					+ this.pConfiguration.getRemoteAddress().getAddress().getPort() + ")";
	
			// Aggiunge una notifica nel log
			loggerNotify(Type.Information, "Apertura canale " + tSocketDescription
					+ " in corso", Verbosity.High);
	
			// Crea la socket
			try {
			
				this.pSocket = new Socket(((SocketFrameAddress) this.pRemoteAddress).getAddress().getAddress(), 
						((SocketFrameAddress) this.pRemoteAddress).getAddress().getPort());

			}
	
			// Segnala eventuali errori legati al nome non valido dell'host
			catch (UnknownHostException e) {
				loggerNotify(Type.Error, "Impossibile aprire il "
						+ tSocketDescription, e.getMessage(), Verbosity.Low);
				tOk = false;
			}
	
			// Segnala eventuali errori di IO
			catch (IOException e) {
				loggerNotify(Type.Error, "Impossibile aprire il "
						+ tSocketDescription, e.getMessage(), Verbosity.Low);
				tOk = false;
			}
	
			// Segnala eventuali altri errori
			catch (Exception e) {
				loggerNotify(Type.Error,
								"Si sono verificati dei problemi durante l'apertura del "
										+ tSocketDescription, e.getMessage(),
								Verbosity.Low);
				tOk = false;
			}
			
		} else {
			tSocketDescription = "socket ";
			tSocketDescription += "(" + ((InetSocketAddress)this.pSocket.getRemoteSocketAddress()).getAddress().getHostAddress() + ", "
			+ this.pSocket.getPort() + ")";
			
		}

		// Se l'apertura e l'inizializzazione della porta sono andate a buon
		// fine..
		if (tOk) {

			this.pLocalAddress 	= new SocketFrameAddress(new InetSocketAddress(this.pSocket.getLocalAddress(), this.pSocket.getLocalPort()));
			this.pRemoteAddress = new SocketFrameAddress(new InetSocketAddress(this.pSocket.getInetAddress(), this.pSocket.getPort()));
			
			// Aggiunge una notifica nel log
			loggerNotify(Type.Information, "Canale " + tSocketDescription
					+ " instaurato con successo!", Verbosity.High);
			
			// .. inizializza i canali di input e di output
			try {
				this.setInputStream(this.pSocket.getInputStream());
				this.setOutputStream(this.pSocket.getOutputStream());
			}

			// .. e segnala eventuali errori
			catch (IOException e) {
				loggerNotify(Type.Error,
						"Impossibile accedere agli stream di IO della socket", Verbosity.Low);
				tOk = false;
			}
		}

		// Se si sono verificati errori, chiude ciò che è rimasto aperto
		if (!tOk) {
			this.pFreeChannel();
			this.setInputStream(null);
			this.setOutputStream(null);
		}

	}

	/**
	 * Funzione di chiusura del canale
	 */
	protected void closeChannel() {

		this.pFreeChannel();
	}

	/**
	 * Funzione per chiudere gli stream aperti e la porta seriale
	 */
	private void pFreeChannel() {

		// Chiude lo stream di input se aperto
		if (this.getInputStream() != null) {
			try {
				this.getInputStream().close();
			} catch (Exception e) {
				loggerNotify(Type.Error,
						"Errore durante chiusura inputstream", e.getMessage(), Verbosity.Low);
			}
		}

		// Chiude lo stream di ouput, se aperto
		if (this.getOutputStream() != null) {
			try {
				this.getOutputStream().close();
			} catch (Exception e) {
				loggerNotify(Type.Error,
						"Errore durante chiusura outputstream", e.getMessage(), Verbosity.Low);
			}
		}

		// Chiude il socket se aperto
		if (this.pSocket != null) {
			try {
				this.pSocket.close();
			} catch (Exception e) {
				loggerNotify(Type.Error,
						"Errore durante chiusura socket", e.getMessage(), Verbosity.Low);
			}
			this.pSocket = null;
		}
	}
	
	public String toString() {
		String s = "";
		s += "+-----------------------------------------------------+\n";
		s += "+ Nome: + " + getName() + "\n";
		if ( this.pLocalAddress != null)
			s += "+ Indirizzo locale " + this.pLocalAddress.toString() + "\n";
		if ( this.pRemoteAddress != null)
		s += "+ Indirizzo remoto " + this.pRemoteAddress.toString() + "\n";
		s += "+-----------------------------------------------------+\n";
		return s;
	} 
	
}
